import 'dart:async';
import 'package:flutter/material.dart';
import '../loading/loading_view.dart';

enum TipDialogType {
  NOTHING,
  LOADING,
  SUCCESS,
  FAIL,
  INFO,
  CUSTOM,
}

class TipDialogIcon extends StatelessWidget {
  final TipDialogType type;
  final Color color;

  const TipDialogIcon({
    Key? key,
    required this.type,
    this.color = Colors.white,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    switch (type) {
      case TipDialogType.SUCCESS:
        return ImageIcon(
          const AssetImage('assets/images/icon_notify_done.png'),
          size: 35.0,
          color: color,
        );
      case TipDialogType.FAIL:
        return ImageIcon(
          const AssetImage('assets/images/icon_notify_error.png'),
          size: 35.0,
          color: color,
        );
      case TipDialogType.INFO:
        return ImageIcon(
          const AssetImage('assets/images/icon_notify_info.png'),
          size: 35.0,
          color: color,
        );
      case TipDialogType.LOADING:
        return LoadingView(size: 35.0, color: color);
      default:
        throw Exception('this type $type is not in DialogType: NOTHING, LOADING, SUCCESS, FAIL, INFO');
    }
  }
}

class TipDialog extends StatelessWidget {
  final String? tip;
  final Widget? icon;
  final WidgetBuilder? _bodyBuilder;
  final Color color;
  final TipDialogType type;

  TipDialog({
    Key? key, 
    this.type = TipDialogType.NOTHING, 
    this.tip
  }) : icon = type == TipDialogType.NOTHING ? null : TipDialogIcon(type: type),
        _bodyBuilder = null, color = const Color(0xbb000000),
        super(key: key);

  TipDialog.customIcon({
    Key? key,
    this.icon,
    this.tip,
  }) : assert(icon != null || tip != null),
       _bodyBuilder = null,
       type = TipDialogType.CUSTOM,
       color = const Color(0xbb000000),
       super(key: key);
  
  TipDialog.builder({
    Key? key,
    WidgetBuilder? bodyBuilder,
    this.color = const Color(0xbb000000),
  }) : this._bodyBuilder = bodyBuilder,
       type = TipDialogType.CUSTOM,
       tip = null,
       icon = null,
       super(key: key);
  
  Widget _buildBody() {
    List<Widget> childs = [];
    if (icon != null) {
      childs.add(Padding(
        padding: tip == null ? const EdgeInsets.all(20) : const EdgeInsets.fromLTRB(0.0, 8.0, 0.0, 8.0),
        child: icon,
      ));
    }
    if (tip != null) {
      childs.add(
        Padding(
          padding: icon == null ? const EdgeInsets.fromLTRB(8.0, 0.0, 8.0, 0.0) : const EdgeInsets.fromLTRB(8.0, 0.0, 8.0, 8.0),
          child: Text(
            tip ?? '',
            textAlign: TextAlign.center,
            style: const TextStyle(color: Colors.white, fontSize: 15.0),
            textDirection: TextDirection.ltr,
          ),
        )
      );
    }
    return Column(
      mainAxisSize: MainAxisSize.min,
      mainAxisAlignment: MainAxisAlignment.center,
      children: childs,
    );
  }
  
  @override
  Widget build(BuildContext context) {
    return ClipRRect(
      borderRadius: BorderRadius.circular(15.0),
      child: Container(
        constraints: icon == null || tip == null 
            ? const BoxConstraints(minHeight: 50.0, minWidth: 100.0)
            : const BoxConstraints(minHeight: 90.0, minWidth: 120.0),
        color: color,
        child: _bodyBuilder == null ? _buildBody() : _bodyBuilder!(context),
      ),
    );
  }
}

typedef OutsideTouchCallback(Widget tipDialog);

class TipDialogContainer extends StatefulWidget {
  final Duration duration;
  final double maskAlpha;
  final bool outsideTouchable;
  final OutsideTouchCallback? onOutsideTouch;

  TipDialogContainer({
    Key? key,
    this.duration = const Duration(seconds: 2),
    this.maskAlpha = 0.3,
    this.outsideTouchable = false,
    required this.onOutsideTouch,  
  }) : super(key: key);

  @override
  State<TipDialogContainer> createState() {
    _TipDialogContainerState state = _TipDialogContainerState();
    TipDialogHelper._init(state);
    return state;
  } 
}

class _TipDialogContainerState extends State<TipDialogContainer> 
        with TickerProviderStateMixin {
  Timer? _timer;
  bool _show = false;
  late AnimationController _animationController;
  late Animation<double> _scaleAnimation;
  late VoidCallback _animationListener;
  bool _prepareDismiss = false;
  late Widget _tipDialog;

  bool _isAutoDismiss = true;
  bool get isShow => _show;

  void dismiss() {
    setState(() {
      if (_prepareDismiss || !_show) { return; }
      if (_animationController.isAnimating) {
        _show = false;
        _animationController.stop(canceled: true);
      } else {
        _prepareDismiss = true;
        _animationController.reverse();
      }
    });
  }

  void show(Widget tipDialog, {bool isAutoDismiss: true}) {
    _tipDialog = tipDialog;
    _isAutoDismiss = isAutoDismiss;
    setState(() {
      _start();
      _show = true;
    });
  }

  @override
  void initState() {
    super.initState();
    _show = false;
    _animationController = AnimationController(
      vsync: this,
      value: 0.0,
      duration: const Duration(milliseconds: 200),
    );
    _animationListener = () {
      if (_animationController.value == 0.0 && _prepareDismiss) {
        setState(() {
          _show = false;
          _prepareDismiss = false;
        });
      }
    };
    _animationController.addListener(_animationListener);
    _scaleAnimation = Tween(begin: 0.95, end: 1.0).animate(_animationController);
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    if (_show) {
      _start();
    }
  }

  void _start() {
    _animationController.forward(from: 0.0);
    if (_isAutoDismiss) {
      if (_timer != null) {
        _timer?.cancel();
        if (_show) {
          dismiss();
        }
        _timer = null;
      }
      _timer = Timer(widget.duration, () {
        dismiss();
        _timer = null;
      });
    }
  }

  @override
  void dispose() {
    if (_timer != null) {
      _timer?.cancel();
      if (_show) {
        dismiss();
      }
    }
    _animationController.removeListener(_animationListener);
    _animationController.dispose();
    super.dispose();
  }

  Widget _buildMaskLayer() {
    return LayoutBuilder(
      builder: (context, size) {
        return FadeTransition(
          opacity: _animationController,
          child: GestureDetector(
            onTap: () {
              if (widget.outsideTouchable) {
                if (widget.onOutsideTouch == null && !_isAutoDismiss) {
                  this.dismiss();
                } else if (widget.onOutsideTouch != null) {
                  widget.onOutsideTouch!(_tipDialog);
                }
              }
            },
            child: Container(
              width: size.maxWidth,
              height: size.maxHeight,
              color: Colors.black.withAlpha(widget.maskAlpha * 255 ~/ 1),
            ),
          ),
        );
      }
    );
  }

  @override
  Widget build(BuildContext context) {
    List<Widget> widgets = [];
    if (_show) {
      widgets.add(_buildMaskLayer());
      widgets.add(ScaleTransition(
        scale: _scaleAnimation,
        child: FadeTransition(
          opacity: _animationController,
          child: _tipDialog,
        ),
      ));
      return Scaffold(
        backgroundColor: Colors.transparent,
        body: Stack(
          alignment: Alignment.center,
          children: widgets,
        ),
      );
    }
    return SizedBox.shrink();
  }
}

class TipDialogHelper {
  static late _TipDialogContainerState _tipDialog;
  static _init(_TipDialogContainerState state) {
    _tipDialog = state;
  }
  static void show(Widget tipDialog, {bool isAutoDismiss: true}) {
    _tipDialog.show(tipDialog, isAutoDismiss: isAutoDismiss);
  }
  static void dismiss() {
    _tipDialog.dismiss();
  }
  static void info(String tip) {
    show(TipDialog(type: TipDialogType.INFO, tip: tip));
  }
  static void fail(String errMsg) {
    show(TipDialog(type: TipDialogType.FAIL, tip: errMsg));
  }
  static void success(String success) {
    show(TipDialog(type: TipDialogType.SUCCESS, tip: success));
  }
  static void loading(String loadingTip) {
    show(TipDialog(type: TipDialogType.LOADING, tip: loadingTip),
        isAutoDismiss: false);
  }
}
